package gateway

import (
	"errors"
	"fmt"
	"gitee.com/wayjin/fabric-extension/cryptoutil"
)

type UserContext struct {
	CryptoPath  string
	MspID  string
}

func (user UserContext) CryptoSuite() (cryptoutil.CryptoSuite, error) {
	//return cryptoutil.NewCryptoSuite(user.CryptoPath, user.MspID)
	return cryptoutil.GetMyCryptoSuiteFromPath(user.CryptoPath, user.MspID, nil)
}

func (user UserContext) GetPeerStatus(peerCtx PeerContext) (PeerStatus, error) {
	cryptoSuite, err := user.CryptoSuite()
	if err != nil {
		return "", err
	}
	peerCli, _ := peerCtx.client()
	return getPeerStatus(cryptoSuite, peerCli)
}

// CreateChannel: create channel in consortium network
func (user UserContext) CreateChannel(channelID, channelConfigFile string, ordererCtx OrdererContext) error  {
	cryptoSuite, err := user.CryptoSuite()
	if err != nil {
		return err
	}
	ordererCli, _ := ordererCtx.client()

	return createChannel(cryptoSuite, ordererCli, channelID, channelConfigFile)
}

func (user UserContext) JoinChannel(ordererCtx OrdererContext, peerCtxs []PeerContext, channelID string) error {
	cryptoSuite, err := user.CryptoSuite()
	if err != nil {
		return err
	}
	ordererCli, _ := ordererCtx.client()
	var peerClients []*peerClient
	for _, peerCtx := range peerCtxs {
		client, err := peerCtx.client()
		if err != nil {
			return err
		}
		peerClients = append(peerClients, client)
	}
	return joinChannel(cryptoSuite, ordererCli, channelID, peerClients)
}

func (user UserContext) GetChannels(peerCtx PeerContext) ([]string, error)  {
	cryptoSuite, err := user.CryptoSuite()
	if err != nil {
		return nil, err
	}
	peerCli, _ := peerCtx.client()
	return listChannel(cryptoSuite, peerCli)
}

func (user UserContext) GetChannelInfo(peerCtx PeerContext, channelID string) (*BlockChainInfo, error) {
	cryptoSuite, err := user.CryptoSuite()
	if err != nil {
		return nil, err
	}
	peerCli, _ := peerCtx.client()
	channelInfo, err := getBlockInfoFromChannel(cryptoSuite, peerCli, channelID)
	if err != nil {
		return nil, err
	}
	return &BlockChainInfo{
		ChannelID:channelID,
		Height:channelInfo.Height,
		CurrentBlockHash:fmt.Sprintf("%x", channelInfo.CurrentBlockHash),
		PreviousBlockHash:fmt.Sprintf("%x", channelInfo.PreviousBlockHash),
	}, nil
}

func (user UserContext) GetChannelBlock(ordererCtx OrdererContext, channelID string, blockNum uint64) (interface{}, error) {
	cryptoSuite, err := user.CryptoSuite()
	if err != nil {
		return nil, err
	}
	ordererCli, _ := ordererCtx.client()
	newestBlock, err := fetchNewestBlockFromChannel(cryptoSuite, ordererCli, channelID)
	if err != nil {
		return nil, err
	}
	if newestBlock.Header.Number < blockNum {
		blockNum = newestBlock.Header.Number
	}
	block, err := fetchBlockFromChannel(cryptoSuite, ordererCli, channelID, blockNum)
	if err != nil {
		return nil, err
	}
	block.String()
	fmt.Println("header number:", block.Header.Number)
	fmt.Printf("header hash: %x\n", block.Header.DataHash)
	fmt.Printf("header prehash: %x\n", block.Header.PreviousHash)

	//blockBytes, err := proto.Marshal(block)
	//if err != nil {
	//	return nil, err
	//}
	//
	//output := new(bytes.Buffer)
	//err = decodeBlockToJSON("common.Block", blockBytes, output)
	//if err != nil {
	//	return nil, err
	//}
	//fmt.Println(output.Bytes())
	return nil, nil
}

func (user UserContext) InvokeChainCode(ccSpec ChaincodeSpec,  ordererCtx OrdererContext, peerCtx PeerContext, channelID string) (*ChainCodeInvokeResponse, error) {
	cryptoSuite, err := user.CryptoSuite()
	if err != nil {
		return nil, err
	}
	ordererCli, _ := ordererCtx.client()
	peerCli, _ := peerCtx.client()
	return invokeChaincode(cryptoSuite, ccSpec, peerCli, ordererCli, channelID)
}

func (user UserContext) QueryChainCode(ccName, ccArgs, channelID string, ordererCtx OrdererContext, peerCtx PeerContext) (*ChainCodeQueryResponse, error) {
	ccSpec := ChaincodeSpec{}
	cryptoSuite, err := user.CryptoSuite()
	if err != nil {
		return nil, err
	}
	ordererCli, _ := ordererCtx.client()
	peerCli, _ := peerCtx.client()
	return queryChainCode(cryptoSuite, ccSpec, peerCli, ordererCli, channelID)
}

// GetPeerStatus: get status of peer
func GetPeerStatus(suite cryptoutil.CryptoSuite, peerCtx PeerContext) (PeerStatus, error) {
	peerCli, err := peerCtx.client()
	if err != nil {
		return "", err
	}
	return getPeerStatus(suite, peerCli)
}

// CreateChannel: create channel
func CreateChannel(suite cryptoutil.CryptoSuite, channelID, channelConfigFile string, ordererCtx OrdererContext) error {
	ordererCli, _ := ordererCtx.client()
	return createChannel(suite, ordererCli, channelID, channelConfigFile)
}

// JoinChannel: peer join channel
func JoinChannel(suite cryptoutil.CryptoSuite,ordererCtx OrdererContext,  peerCtxs []PeerContext, channelID string) error {
	ordererCli, _ := ordererCtx.client()
	var peerClients []*peerClient
	for _, peerCtx := range peerCtxs {
		client, err := peerCtx.client()
		if err != nil {
			return err
		}
		peerClients = append(peerClients, client)
	}
	return joinChannel(suite, ordererCli, channelID, peerClients)
}

// ListChannels: list joined channel of peer
func ListChannels(suite cryptoutil.CryptoSuite, peerCtx PeerContext) ([]string, error) {
	peerCli, _ := peerCtx.client()
	return listChannel(suite, peerCli)
}

// GetChannelInfo: get channel info
func GetChannelInfo(suite cryptoutil.CryptoSuite, channelID string, peerCtx PeerContext) (*BlockChainInfo, error) {
	peerCli, _ := peerCtx.client()
	channelInfo, err := getBlockInfoFromChannel(suite, peerCli, channelID)
	if err != nil {
		return nil, err
	}
	return &BlockChainInfo{
		ChannelID:channelID,
		Height:channelInfo.Height,
		CurrentBlockHash:fmt.Sprintf("%x", channelInfo.CurrentBlockHash),
		PreviousBlockHash:fmt.Sprintf("%x", channelInfo.PreviousBlockHash),
	}, nil
}

func GetBlock(suite cryptoutil.CryptoSuite, channelID string, ordererCtx OrdererContext, blkNum uint64) (*BlockInfo, error) {
	ordererCli, _ := ordererCtx.client()
	newestBlock, err := fetchNewestBlockFromChannel(suite, ordererCli, channelID)
	if err != nil {
		return nil, err
	}

	if newestBlock.Header.Number < blkNum {
		return nil, errors.New("block number is larger than newest block")
	}

	block, err := fetchBlockFromChannel(suite, ordererCli, channelID, blkNum)
	if err != nil {
		return nil, err
	}
	return ExtractBlock(block)
}

func GetNewestBlock(suite cryptoutil.CryptoSuite, channelID string, ordererCtx OrdererContext) (*BlockInfo, error) {
	ordererCli, _ := ordererCtx.client()
	fetchNewestBlockFromChannel(suite, ordererCli, channelID)
	// TODO
	return nil, nil
}

// GetTopCountBlocks: get newest count blocks
func GetTopCountBlocks(suite cryptoutil.CryptoSuite, channelID string, topCount uint64, ordererCtx OrdererContext) ([]BlockSummary, error) {
	ordererCli, _ := ordererCtx.client()
	blocks, err := fetchTopCountBlocks(suite, ordererCli, channelID, topCount)
	if err != nil {
		return nil, err
	}
	if len(blocks) == 0 {
		return nil, nil
	}
	fmt.Println("count:", len(blocks))
	blockHeaders := make([]BlockSummary, len(blocks))
	//for _, b := range blocks {
	//	header := BlockSummary{
	//		Number:   b.Header.Number,
	//		PreHash:  HashToString(b.Header.PreviousHash),
	//		DataHash: HashToString(b.Header.DataHash),
	//	}
	//	blockHeaders = append(blockHeaders, header)
	//}
	for i:=0; i<len(blocks); i++ {
		blockHeaders[i] = BlockSummary{
			Number:   blocks[i].Header.Number,
			PreHash:  HashToString(blocks[i].Header.PreviousHash),
			DataHash: HashToString(blocks[i].Header.DataHash),
		}
	}
	return blockHeaders, nil
}

func InstallChainCode(suite cryptoutil.CryptoSuite, peerCtx PeerContext, ccname, ccversion, ccPath string) error {
	peerCli, err := peerCtx.client()
	if err != nil {
		return err
	}
	spec := ChaincodeSpec{Name:ccname, Version:ccversion, Path:ccPath}
	return installChaincode(suite, peerCli, spec)
}

func ListChainCodes(suite cryptoutil.CryptoSuite, channelID string, instantaited bool, peerCtx PeerContext) (*ChainCodeListResponse, error){
	peerCli, _ := peerCtx.client()
	chaincodes, err := listChaincode(suite, peerCli, true, instantaited, channelID)
	if err != nil {
		return nil, err
	}
	return &ChainCodeListResponse{Count:len(chaincodes), ChainCodes:chaincodes}, nil
}

func InvokeChainCode(suite cryptoutil.CryptoSuite, channelID string, ccSpec ChaincodeSpec,  ordererCtx OrdererContext, peerCtx PeerContext) (*ChainCodeInvokeResponse, error) {
	ordererCli, _ := ordererCtx.client()
	peerCli, _ := peerCtx.client()
	return invokeChaincode(suite, ccSpec, peerCli, ordererCli, channelID)
}

func QueryChainCode(suite cryptoutil.CryptoSuite, channelID string, ccSpec ChaincodeSpec, ordererCtx OrdererContext, peerCtx PeerContext) (*ChainCodeQueryResponse, error) {
	ordererCli, _ := ordererCtx.client()
	peerCli, _ := peerCtx.client()

	return queryChainCode(suite, ccSpec, peerCli, ordererCli, channelID)
}